ひとりNavigation API Advent Calendar 05日目
https://gyazo.com/4eaa22c747fba5269fe6d04c5c0ac340
これはひとりNavigation API Advent Calendarの5日目です
History APIの問題点についてを触れていきます
「The case for the new Web History API」( https://github.com/dvoytenko/web-history-api/ ) の非公式日本語訳 · GitHub
Geminiに翻訳してもらいました
History APIの使用を必要とする主要なユースケースには、以下のようなものがある
シングルページアプリケーション(SPA)向けのWebアプリルーター
クライアントサイドルーターはナビゲーションを捕捉し、必要な読み込みとレンダリングを同じウィンドウ内で実行し、最終的にアドレスバーのURLと履歴スタックを更新します。
ポップアップやダイアログなどのUIオーバーレイコンポーネント
通常、意味のあるアドレスバーの変更は伴いませんが、オーバーレイは「戻る」ボタンを使用してキャンセル可能である必要があります
例えば、リッチな日付選択ポップアップでユーザーが「戻る」ボタンをクリックした際、ポップアップが閉じるのではなく、ブラウザが前のページに戻ってしまうのは悪いUX(ユーザー体験)です
一時的な history.state
BFCache(バック・フォワード・キャッシュ)からの高速なリロードを行うための有用なストレージとなり得ます
昔ながらの <a href="#..."> によるフラグメントナビゲーション
しかしHistory APIには以下のような重大な欠点がある
1. history.state の信頼性が低い
Webアプリの予期しないタイミングで消失する可能性がある
例えば、フラグメントナビゲーションやiframe内でのプッシュ発生時など
2. history.state がスタックとして機能しない
本来、スタックのプロパティは、明示的に上書きされない限り新しい状態(state)にも再帰的に適用されるべき
しかし history.state は、ナビゲーションのたびに状態を完全に置き換えてしまう
3. popstate イベントが理にかなっていない
このイベントはポップ(戻る/進む)だけでなく、フラグメントナビゲーション(これは「プッシュ」操作)でも発火する
また、window.history.go(-2) などを実行した際の中間のポップ処理を行うことが不可能
4. 履歴イベントがキャンセルできない
「保存せずに移動してよろしいですか?」という機能を実装するには、すべての履歴変更をバッファリングし、プッシュし直すという処理が必要になる
5. クライアントサイドナビゲーションの実装が複雑すぎる
Webアプリは結局、グローバルなクリックイベントを捕捉し、プログラムでナビゲーションを行うための特別なAPIを自作することになる
6. 履歴とナビゲーションの同期・非同期のセマンティクスが予測不能
7. iframeの問題: iframeがアプリケーションの履歴スタックを完全に台無しにしてしまう可能性がある
以上を読んだ上でのyamanoku.iconの反芻
history.stateの問題は以下のパターンが挙げられている
code:js
window.history.pushState({a: 1}, '', '');
// これで状態がわかる!
window.history.state.a === 1
// ユーザーが <a href="#b"> でフラグメントへ移動する
// おっと。状態が失われた。
window.history.state === null
つまるところユーザー操作によって簡単に履歴やその状態が消えてしまうこと
これまでもっていた情報(主に状態)がまっさらになってしまうのかなりつらい
SPAのルーティング操作と<a>や自前のHistory API実装とかと混ぜるとめんどくさいことになりそう
完全にSPAの中に閉じれるといいけどそういうユースケースばかりではないよなぁ
この辺が使いづらいってところに直結してそう
履歴イベントがキャンセルできない
これについてはブラウザの機能に頼らずに自前実装するのはしんどそうに思った
サンプルを提示してもらった
code:js
// フォームに変更があるかどうかのフラグ
let hasUnsavedChanges = true;
// 現在のURL(ページロード時またはState更新時に保存しておく)
let currentUrl = window.location.href;
let currentState = window.history.state;
window.addEventListener('popstate', (event) => {
// 注意: このイベントが発火した時点で、ブラウザのURLは既に「前のページ」に変わっています!
if (hasUnsavedChanges) {
// ユーザーに確認を求める
const shouldLeave = confirm("保存されていない変更があります。移動してもよろしいですか?");
if (!shouldLeave) {
// ユーザーが「キャンセル(このページに留まる)」を選んだ場合
// 【ここが問題の箇所】
// すでにブラウザは「戻る」を実行してしまっているので、
// プログラムで「元のページ(今のページ)」を履歴スタックに
// もう一度プッシュして、無理やりURLを戻す必要があります。
window.history.pushState(currentState, '', currentUrl);
// これにより、見た目上はページに留まったように見えますが、
// 実際には履歴スタックが1つ増えています。
} else {
// 移動してOKなら、何もしない(そのまま戻る処理が完了している)
}
}
});
だるい!!!!!!!!!!!!
そしてiframeって本当に怖いね
違うコンテキストが同じ画面内にあって同じフリしてるんだから